/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.skatiger.jrove.servlet;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Principal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.el.ELResolver;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.skatiger.jrove.ExternalContext;
import org.skatiger.jrove.el.implicaitobject.ApplicationImplicitObject;
import org.skatiger.jrove.el.implicaitobject.ApplicationScopeImplicitObject;
import org.skatiger.jrove.el.implicaitobject.CookieImplicitObject;
import org.skatiger.jrove.el.implicaitobject.HeaderImplicitObject;
import org.skatiger.jrove.el.implicaitobject.HeaderValuesImplicitObject;
import org.skatiger.jrove.el.implicaitobject.ImplicitObject;
import org.skatiger.jrove.el.implicaitobject.InitParamImplicitObject;
import org.skatiger.jrove.el.implicaitobject.JroveImplicitObjectResolver;
import org.skatiger.jrove.el.implicaitobject.ParamImplicitObject;
import org.skatiger.jrove.el.implicaitobject.ParamValuesImplicitObject;
import org.skatiger.jrove.el.implicaitobject.RequestImplicitObject;
import org.skatiger.jrove.el.implicaitobject.RequestScopeImplicitObject;
import org.skatiger.jrove.el.implicaitobject.SessionImplicitObject;
import org.skatiger.jrove.el.implicaitobject.SessionScopeImplicitObject;
import org.skatiger.jrove.servlet.enumeration.EnumerationIterator;
import org.skatiger.jrove.util.AssertUtil;

public final class ServletExternalContext implements ExternalContext {
    private static final String INIT_PARAMETER_MAP_ATTRIBUTE = InitParameterMap.class
            .getName();

    private ServletContext _servletContext;
    private ServletRequest _servletRequest;
    private ServletResponse _servletResponse;
    private Map<String, Object> _applicationMap;
    private Map<String, Object> _sessionMap;
    private Map<String, Object> _requestMap;
    private Map<String, String> _requestParameterMap;
    private Map<String, String[]> _requestParameterValuesMap;
    private Map<String, String> _requestHeaderMap;
    private Map<String, String[]> _requestHeaderValuesMap;
    private Map<String, Cookie> _requestCookieMap;
    private Map<String, String> _initParameterMap;
    private HttpServletRequest _httpServletRequest;
    private HttpServletResponse _httpServletResponse;
    private String _requestServletPath;
    private String _requestPathInfo;

    public ServletExternalContext(final ServletContext servletContext,
            final ServletRequest servletRequest,
            final ServletResponse servletResponse) {
        _servletContext = servletContext;
        _servletRequest = servletRequest;
        _servletResponse = servletResponse;
        _applicationMap = null;
        _sessionMap = null;
        _requestMap = null;
        _requestParameterMap = null;
        _requestParameterValuesMap = null;
        _requestHeaderMap = null;
        _requestHeaderValuesMap = null;
        _requestCookieMap = null;
        _initParameterMap = null;
        _httpServletRequest = isHttpServletRequest(servletRequest) ? (HttpServletRequest) servletRequest
                : null;
        _httpServletResponse = isHttpServletResponse(servletResponse) ? (HttpServletResponse) servletResponse
                : null;
        if (_httpServletRequest != null) {
            // HACK: MultipartWrapper scrambles the servletPath for some reason
            // in Tomcat 4.1.29 embedded in JBoss
            // 3.2.3!?
            // (this was reported by frederic.auge [frederic.auge@laposte.net])
            _requestServletPath = _httpServletRequest.getServletPath();
            _requestPathInfo = _httpServletRequest.getPathInfo();
        }
    }

    private void checkHttpServletRequest() {
        if (_httpServletRequest == null) {
            throw new UnsupportedOperationException(
                    "Only HttpServletRequest supported");
        }
    }

    private void checkHttpServletResponse() {
        if (_httpServletResponse == null) {
            throw new UnsupportedOperationException(
                    "Only HttpServletResponse supported");
        }
    }

    public Map<String, Object> getApplicationMap() {
        if (_applicationMap == null) {
            _applicationMap = new ApplicationMap(_servletContext);
        }
        return _applicationMap;
    }

    public String getAuthType() {
        checkHttpServletRequest();
        return _httpServletRequest.getAuthType();
    }

    public Object getContext() {
        return _servletContext;
    }

    public String getInitParameter(final String s) {
        return _servletContext.getInitParameter(s);
    }

    @SuppressWarnings("unchecked")
    public Map<String, String> getInitParameterMap() {
        if (_initParameterMap == null) {
            // We cache it as an attribute in ServletContext itself (is this
            // circular reference a problem?)
            if ((_initParameterMap = (Map<String, String>) _servletContext
                    .getAttribute(INIT_PARAMETER_MAP_ATTRIBUTE)) == null) {
                _initParameterMap = new InitParameterMap(_servletContext);
                _servletContext.setAttribute(INIT_PARAMETER_MAP_ATTRIBUTE,
                        _initParameterMap);
            }
        }
        return _initParameterMap;
    }

    public String getRemoteUser() {
        checkHttpServletRequest();
        return _httpServletRequest.getRemoteUser();
    }

    public Object getRequest() {
        return _servletRequest;
    }

    public String getRequestCharacterEncoding() {
        return _servletRequest.getCharacterEncoding();
    }

    public String getRequestContentType() {
        return _servletRequest.getContentType();
    }

    public String getRequestContextPath() {
        checkHttpServletRequest();
        return _httpServletRequest.getContextPath();
    }

    public Map<String, Cookie> getRequestCookieMap() {
        if (_requestCookieMap == null) {
            checkHttpServletRequest();
            _requestCookieMap = new CookieMap(_httpServletRequest);
        }
        return _requestCookieMap;
    }

    public Map<String, String> getRequestHeaderMap() {
        if (_requestHeaderMap == null) {
            checkHttpServletRequest();
            _requestHeaderMap = new RequestHeaderMap(_httpServletRequest);
        }
        return _requestHeaderMap;
    }

    public Map<String, String[]> getRequestHeaderValuesMap() {
        if (_requestHeaderValuesMap == null) {
            checkHttpServletRequest();
            _requestHeaderValuesMap = new RequestHeaderValuesMap(
                    _httpServletRequest);
        }
        return _requestHeaderValuesMap;
    }

    public Locale getRequestLocale() {
        return _servletRequest.getLocale();
    }

    @SuppressWarnings("unchecked")
    public Iterator<Locale> getRequestLocales() {
        checkHttpServletRequest();
        return new EnumerationIterator<Locale>(_httpServletRequest.getLocales());
    }

    public Map<String, Object> getRequestMap() {
        if (_requestMap == null) {
            _requestMap = new RequestMap(_servletRequest);
        }
        return _requestMap;
    }

    public Map<String, String> getRequestParameterMap() {
        if (_requestParameterMap == null) {
            _requestParameterMap = new RequestParameterMap(_servletRequest);
        }
        return _requestParameterMap;
    }

    @SuppressWarnings("unchecked")
    public Iterator<String> getRequestParameterNames() {
        return new EnumerationIterator<String>(
                _servletRequest.getParameterNames());
    }

    public Map<String, String[]> getRequestParameterValuesMap() {
        if (_requestParameterValuesMap == null) {
            _requestParameterValuesMap = new RequestParameterValuesMap(
                    _servletRequest);
        }
        return _requestParameterValuesMap;
    }

    public String getRequestPathInfo() {
        checkHttpServletRequest();
        // return (_httpServletRequest).getPathInfo();
        // HACK: see constructor
        return _requestPathInfo;
    }

    public String getRequestServletPath() {
        checkHttpServletRequest();
        // return (_httpServletRequest).getServletPath();
        // HACK: see constructor
        return _requestServletPath;
    }

    public URL getResource(final String path) throws MalformedURLException {
        AssertUtil.notNull(path, "path must be not null.");
        return _servletContext.getResource(path);
    }

    public InputStream getResourceAsStream(final String path) {
        AssertUtil.notNull(path, "path must be not null.");
        return _servletContext.getResourceAsStream(path);
    }

    @SuppressWarnings("unchecked")
    public Set<String> getResourcePaths(final String path) {
        AssertUtil.notNull(path, "path must be not null.");
        return _servletContext.getResourcePaths(path);
    }

    public Object getResponse() {
        return _servletResponse;
    }

    public String getResponseCharacterEncoding() {
        return _servletResponse.getCharacterEncoding();
    }

    public String getResponseContentType() {
        return _servletResponse.getContentType();
    }

    public Object getSession(boolean create) {
        checkHttpServletRequest();
        return ((HttpServletRequest) _servletRequest).getSession(create);
    }

    public Map<String, Object> getSessionMap() {
        if (_sessionMap == null) {
            checkHttpServletRequest();
            _sessionMap = new SessionMap(_httpServletRequest);
        }
        return _sessionMap;
    }

    public Principal getUserPrincipal() {
        checkHttpServletRequest();
        return _httpServletRequest.getUserPrincipal();
    }

    private boolean isHttpServletRequest(final ServletRequest servletRequest) {
        return servletRequest instanceof HttpServletRequest;
    }

    private boolean isHttpServletResponse(final ServletResponse servletResponse) {
        return servletResponse instanceof HttpServletResponse;
    }

    public boolean isUserInRole(final String role) {
        AssertUtil.notNull(role, "role  must be not null.");
        checkHttpServletRequest();
        return _httpServletRequest.isUserInRole(role);
    }

    public void log(final String message) {
        AssertUtil.notNull(message, "message  must be not null.");
        _servletContext.log(message);
    }

    public void log(final String message, final Throwable exception) {
        AssertUtil.notNull(message, "message must be not null.");
        AssertUtil.notNull(exception, "exception must be not null.");
        _servletContext.log(message, exception);
    }

    public void release() {
        _servletContext = null;
        _servletRequest = null;
        _servletResponse = null;
        _applicationMap = null;
        _sessionMap = null;
        _requestMap = null;
        _requestParameterMap = null;
        _requestParameterValuesMap = null;
        _requestHeaderMap = null;
        _requestHeaderValuesMap = null;
        _requestCookieMap = null;
        _initParameterMap = null;
        _httpServletRequest = null;
    }

    public void setRequestCharacterEncoding(final java.lang.String encoding) throws java.io.UnsupportedEncodingException {
        this._servletRequest.setCharacterEncoding(encoding);
    }

    public void setResponseCharacterEncoding(final String encoding) {
        this._servletResponse.setCharacterEncoding(encoding);
    }

    public String encodeURL(String urlStr) {
        checkHttpServletResponse();
        return (_httpServletResponse).encodeURL(urlStr);
    }

}